package it.unicam.cs.cosy.bioshape.core.components;

import hermesV3.Message;
import hermesV3.agent.Identifier;
import hermesV3.agent.InteractionContainer;
import hermesV3.agent.UserAgent;
import hermesV3.protocols.PhantomProtocol;
import hermesV3.protocols.SplitSpaceProtocol;
import hermesV3.protocols.StepProtocol;
import hermesV3.services.HubService.SimulationAgent;
import hermesV3.services.Space.SpaceManager;
import hermesV3.util.HermesLogger;
import it.unicam.cs.cosy.bioshape.core.collisiondetection.broadphase.BroadPhase;
import it.unicam.cs.cosy.bioshape.core.collisiondetection.broadphase.SAPBroadPhase;
import it.unicam.cs.cosy.bioshape.core.collisiondetection.narrowphase.NarrowPhase;
import it.unicam.cs.cosy.bioshape.core.collisiondetection.narrowphase.SearchingErrorException;
import it.unicam.cs.cosy.bioshape.core.collisiondetection.narrowphase.VClipCollisionDetector;
import it.unicam.cs.cosy.bioshape.core.collisiondetection.narrowphase.VClipNarrowPhase;
import it.unicam.cs.cosy.bioshape.core.collisionresponse.CollisionResponder;
import it.unicam.cs.cosy.bioshape.core.collisionresponse.ImpulseCollisionResponder;
import it.unicam.cs.cosy.bioshape.core.shapes.BoundingBox;
import it.unicam.cs.cosy.bioshape.core.util.Debug;
import it.unicam.cs.cosy.bioshape.core.util.Messages;
import it.unicam.cs.cosy.bioshape.gui.visualizer3d.Command;

import java.util.ArrayList;
import java.util.List;

import javax.vecmath.Point3d;
import javax.vecmath.Vector3d;

import vclip.PolyTree;

/**
 * Implements the controlling logic over a simulation node. In detail, it checks
 * entities for collisions into its node, i.e. over the space section assigned
 * to this coordinator.
 * 
 * @author Federico Buti
 */
public class LocalCoordinator extends UserAgent {

    /** Auto-generated serial ID */
    private static final long serialVersionUID = 397730681864070775L;

    /** The simulation spatial and temporal scale */
    private Scale scale;

    /** {@code Identifier} to communicate with the {@code GlobalCoordinator} */
    private Identifier gcID;

    // private double maxPerception;
    // private double maxTranslation;
    // private double maxRotation;
    /** {@code Bounds} associated to the space of this {@code LocalCoordinator} */
    private BoundingBox lcBoundingBox;

    /** {@code Arraylist} of {@code WallEntity} shapes: for fast checks */
    private ArrayList<WallEntity> localWalls;

    /** {@code Identificator} to communicate with the {@code SpaceManager} */
    private Identifier spaceId = null;

    /**
     * {@code Entity} objects controlled by the {@code Local Coordinator} can be
     * <i>centring</i> or <i>phantom</i>.
     */
    private List<Entity> entityList;

    /**
     * {@code List} of {@code Entity} couples which collided on the current
     * iteration
     */
    List<EntityPair> pairsCollided;

    /** Represents the simulation time elapsed in this {@code LocalCoordinator}. */
    private double myTime = 0;

    /** Entry point for the Broad-phase module. */
    private BroadPhase broadPhaseModule;

    /** Entry point for the Narrow-phase module. */
    private NarrowPhase narrowPhaseModule;

    /** Entry point for the Collision Response module. */
    private CollisionResponder collisionResponder = new ImpulseCollisionResponder();

    /** The current iteration {@code First time of split}. */
    private double iterationFts;

    /** The current iteration {@code First time of contact}. */
    private double iterationFtc;

    /** List of {@code Entity} objects received from the {@code SpaceManager}. */
    private List<Entity> receivedEntities;

    /** List of {@code Entity} objects sent to the {@code SpaceManager}. */
    private List<Entity> sentEntities;

    /**
     * Initial {@code Entity} object ID assigned to this
     * {@code LocalCoordinator}. Used To generate the shapes with different IDs
     * all over the simulated space.
     */
    private int ids;

    /** The duration of the current iteration. */
    private double delta;

    /** {@code Identifier} associated with the {@code SimulationAgent}. */
    private Identifier inputId;

    /** boolean check for simulation continuation. */
    // TODO fare una serie di costanti esterne: CONTINUE PAUSE STOP
    private boolean simulate;

    /**
     * {@code List} of {@code Commands} for the visualisation: updated each
     * iteration.
     */
    private List<Command> iterationCommands;

    // TODO Document!
    private int iteration;
    
    /*
     * To check if we are in a fixpoint!
     */
    private boolean fixpoint = false;

    /**
     * Builds the {@code LocalCoordinator} according to the way Hermes builds a
     * {@code UserAgent}.
     * 
     * @param name
     *            a {@code String} representing the name of the
     *            {@code LocalCoordinator}
     * @param inputId
     *            Agents controlling the {@code GlobalCoordinator} GUI. It also
     *            controls <i>all</i> the {@code LocalCoordinator}s involved in
     *            the simulation.
     */
    public LocalCoordinator(String name, Identifier inputId) {
        super(name);
        this.inputId = inputId;
        // creation of the lists of entities
        entityList = new ArrayList<Entity>();
        receivedEntities = new ArrayList<Entity>();
        sentEntities = new ArrayList<Entity>();
        localWalls = new ArrayList<WallEntity>();
    }

    /**
     * Builds the {@code LocalCoordinator} according to the way Hermes builds a
     * {@code UserAgent}.
     * 
     * @param name
     *            a {@code String} representing the name of the
     *            {@code LocalCoordinator}
     * @param gcID
     *            {@code Identifier} of the {@code GlobalCoordinator}
     * @param smID
     *            {@code Identifier} of the {@code SpaceManager}
     * @param inputId
     *            {@code Identifier} of the {@code SimulationAgent}
     * 
     * @see Identifier
     * @see GlobalCoordinator
     * @see SpaceManager
     * @see SimulationAgent
     */
    public LocalCoordinator(String name, Identifier gcID, Identifier smID,
            Identifier inputId) {
        super(name);
        this.gcID = gcID;
        this.spaceId = smID;
        this.inputId = inputId;
        // creation of the lists of entities
        entityList = new ArrayList<Entity>();
        receivedEntities = new ArrayList<Entity>();
        sentEntities = new ArrayList<Entity>();
        localWalls = new ArrayList<WallEntity>();
    }

    @SuppressWarnings("unchecked")
    @Override
    public void init() {
        reception();
        // 1. Send acknowledgement to the GlobalCoordinator
        boolean status = Messages.sendAck(this, gcID);
        if (!status) {
            HermesLogger.log(HermesLogger.ERROR, this.getAgentName()
                    + " - ---> GlobalCoordinator Agent communication error"
                    + "...Simulation stopped!");
            terminate();
            return;
        }
        //
        // 2. Awaits data from SpaceManager to set-up simulation: walls, bounds
        // entities and scale
        if (!receiveWalls())
            return;
        Messages.sendAck(this, spaceId);
        //
        Object[] data = getSimulationData();
        if (data == null)
            return;
        ArrayList<Point3d> bounds = (ArrayList<Point3d>) data[0];
        lcBoundingBox = new BoundingBox(bounds.get(0), bounds.get(1));
        List<ProtoEntity> entityDataList = (ArrayList<ProtoEntity>) data[1];
        scale = ((Scale) data[2]);
        ids = ((Integer) data[3]).intValue();
        // 3. Creates real entities from proto-entities and logs their position
        generateEntities(entityDataList);
        snapSpace(true);
        // 4. receive info for simulation from GC and sets up broad-phase
        broadPhaseModule = new SAPBroadPhase(entityList);
        // narrowPhaseModule = new VClipNarrowPhase(entityList.size());
        narrowPhaseModule = new VClipCollisionDetector();
        // 5. Send the log to the GC
        status = Messages.sendInteraction(this, gcID, new StepProtocol(),
                StepProtocol.INTERACTIONSENDSTATUS, iterationCommands);
        iterationCommands.clear();
        if (!status) {
            Messages.sendError(this, gcID, "---> SpaceManager Agent "
                    + "communication error...Simulation stopped!");
            HermesLogger.log(HermesLogger.ERROR, this.getAgentName()
                    + " - ---> SpaceManager Agent communication error"
                    + "...Simulation stopped!");
            return;
        }
        // 6. communicates the end of the setup
        status = Messages.sendAck(this, spaceId);
        if (!status) {
            Messages.sendError(this, gcID, "---> SpaceManager Agent "
                    + "communication error...Simulation stopped!");
            HermesLogger.log(HermesLogger.ERROR, this.getAgentName()
                    + " - ---> SpaceManager Agent communication error"
                    + "...Simulation stopped!");
            return;
        }
        // 7. Simulation cycle
        simulate = true;
        while (simulate)
            localSimulationMainLoop();
    }

    /**
     * Produces a "snapshot" of the {@code Entity} in the simulation for this
     * {@code LocalCoordinator}. Obviously, {@code WallEntity} objects are not
     * logged as they are only used to bound the simulation. Moreover,
     * {@code Entity} objects which are phantom are also not logged as they are
     * by the {@code LocalCoordinator} for which they are centering.
     */
    private void snapSpace(boolean flag) {
        int numComs = entityList.size();
        iterationCommands = new ArrayList<Command>(numComs);
        for (Entity e : entityList) {
            if (e instanceof WallEntity
            // TODO Luca: per il momento tolgo questo controllo che non funziona
            // quando
            // le entity vanno con il centro fuori dai limiti del mondo e c'è un
            // local
            // coordinator solo, cioè niente phantoms
            // || !lcBoundingBox.intersect(e.getCenter())
            )
                continue; // wall and phantom are not stored!!
            // if (((ShapedEntity) e).isCompound()) {
            // TODO Luca: Finché non introdurrò i compound 3D processes, anche
            // le
            // compound shapes le considero non compound e mando una sola shape,
            // senza
            // pezzi (tanto non si dovrà mai splittare)
            // for (Entity eC : e.getComponentEntities()) {
            // for (PolyTree eC : e.getComponentEntities()) {
            // System.out.println("generating command for PolyTree " + eC);
            // iterationCommands.add(createCommand(eC, flag));
            // }
            // iterationCommands.add(createCommand(e, flag));
            // } else {
            // Luca
            // System.out.println("generating command for Non Compound Entity "
            // + e);
            iterationCommands.add(createCommand(e, flag));
            // }
        }
    }

    private Command createCommand(Entity eC, boolean flag) {
        if (flag) {
            // Clear the name of the geometry from the final "#id" part that was
            // added when the instance of the Entity was created from
            // ProtoEntity
            String geometryName = eC.getGeometry();
            geometryName = geometryName.substring(0, geometryName.indexOf('#'));
            return new Command(iteration, eC.getName(), geometryName,
                    eC.getTransform());
        }
        return new Command(iteration, eC.getName(), eC.getTransform());
    }

    // TODO Luca: il marasma è qui!!! fixme
    // private Command createCommand(PolyTree eC, boolean flag) {
    // if (flag)
    // return new Command(iteration, eC.getName(), eC.getName(), // c'era
    // "simple"
    // eC.getTransform());
    // return new Command(iteration, eC.getName(), eC.getTransform());
    // }

    /**
     * Creates the simulation entities from the given {@code ProtoEntity}
     * objects read as input.
     * 
     * For each {@code ProtoEntity}, only the entities with center in the
     * current LC are generated. The {@code id} assigned to the entity is
     * generated by the LC and it is in a range assigned from the
     * {@code SpaceManager} to the LC itself.
     * 
     * @param entityDataList
     *            List of {@code ProtoEntity} objects assigned to this
     *            coordinator.
     */
    private void generateEntities(List<ProtoEntity> entityDataList) {
        for (ProtoEntity e : entityDataList) {
            for (Vector3d center : e.getCenters()) {
                if (!lcBoundingBox.intersect(center))
                    continue; // ELSE salta le phantom
                entityList.add(e.generateEntity(ids++, center));
            }
        }
    }

    /**
     * Checks if the entities have collided in the previous timestep. Collided
     * entities has the collision flag reset for the current timestep whereas
     * the other entities are updated according to the associated timestep.
     * 
     */
    public void updateVelocities() {
        for (Entity s : this.entityList) {
            // Luca: controllo demandato a ShapedEntity
            // if (s.isCollided())
            // s.setCollided(false);
            // else
            s.updateVelocity();
        }
    }

    /**
     * Executes the <i>simulated</i> movement by updating the {@code Bounds}
     * positions. Such positions are used to execute the broad-phase collision
     * detection and detect <i>possible</i> collisions. Note that no check is
     * executed to not move {@code WallEntity} bounds: since they are
     * <i>motionless</i> (i.e. their velocity is <i>always</i> zero) the method
     * does <i>not</i> move the bounds.
     * 
     * @param delta
     *            The timestep received from the {@code GlobalCoordinator} for
     *            the current iteration
     * 
     * @see Entity#updateVelocity()
     * @see it.unicam.cs.cosy.bioshape.core.collisiondetection.broadphase.BroadPhase
     */
    private void simulateMovement(double delta) {
        for (Entity e : entityList) {
            it.unicam.cs.cosy.bioshape.core.shapes.Bounds b = e.getBounds();
            // NO check for WallEntity objects: translation is always ZERO.
            Vector3d v = e.getInstantVelocity();
            v.scale(delta);
            b.translateOf(v);
        }
    }

    /**
     * Implements {@code BioShape} MAIN LOOP according to
     * "A multiscale meta-model for BioShape" paper. Implements the operation
     * not included in the {@code GlobalCoordinator#simulationMainLoop()}
     * method.
     */
    private void localSimulationMainLoop() {
        iterationFts = Double.POSITIVE_INFINITY;
        iterationFtc = Double.POSITIVE_INFINITY;
        // System.out.println(System.currentTimeMillis());
        delta = receiveDelta();
        if (delta == Double.NEGATIVE_INFINITY) {
            simulate = false;
            return;
        }
        // increment the interactions counter
        iteration++;
        // TODO check for split
        // -- non collision-driven interactions --
        ncdBehaviour();
        // -- phantom deletion --
        // reset();
        // -- calculate FTS --
        // iterationFts = getFTS(); //TODO togliere commento!!
        // -- calculate FTC --
        updateVelocities();
        simulateMovement(delta);
        collectPhantoms();
        sendEntitiesToCheck();
        receivePhantoms();
        // Debug code
        if (Debug.countSteps > 10) {
            int pippo = 0;
            pippo = pippo +1;
            pippo = pippo +1;
        }         
        List<EntityPair> pairs = broadPhaseModule.getOverlappingPairs();
        try {
            // if (pairs.size() != 0)
            iterationFtc = narrowPhaseModule.getFTC(pairs, delta);
            
            if (iterationFtc == 0)
                if (!this.fixpoint)
                    this.fixpoint = !this.fixpoint;
                else {
                    this.fixpoint = false; // for other future simulations!
                    throw new SearchingErrorException("Two consecutive ftc = 0");
                }
            else this.fixpoint = false; // reset
            // else
            // iterationFtc = Double.POSITIVE_INFINITY;
        } catch (SearchingErrorException e) {
            HermesLogger.log(HermesLogger.ERROR, getClass().getSimpleName()
                    + " - " + "Error while searching for the first time of"
                    + " contact. The simulation will be aborted. "
                    + "Please review your input....");
            Messages.sendAck(this, inputId);
            simulate = false;
            // return;
        }
        // -- calculate shortest step --
        sendSmallestStep();
        double trueStep = receiveRealStep();
        // -- simulation advance --
        advance(trueStep);
        snapSpace(false);
        // -- end of the iteration --
        endCurrentIteration();
    }

    /**
     * Calculates the FTS, i.e. the earliest time after which a split occurs.
     * 
     * @return the time interval after which the earliest splitting occurs
     */
    private double getFTS() {
        // TODO completare con il calcolo dell'FTS
        return 0;
    }

    /**
     * Realizes non collision-driven interactions among the controlled entities.
     * The procedure is executed <i>only</i> on centring entities.
     */
    private void ncdBehaviour() {
        // TODO check for phantom: only centering fanno ncdbehaviour
        for (Entity e : entityList) {
            ShapeRequest request = e.queryServices();
            // TODO data la shaperequest, dobbiamo riempirla di dati! Per farlo
            // mandiamo il tutto al GC! È lui che si occupa de ste informazioni,
            // e chi altri sennò??
            // 1) la perception va caricata sull'arraylist passato (in formato
            // di pairs). Se ci sono, si caricano solo quelle selezionate,
            // altrimenti si carica tutto il leggibile.
            // 2) Gli altri servizi si interrogano prendendo gli elementi della
            // lista di input, questa volta non pair!
        }
    }

    /**
     * Executes the clean up, after the perception operations have been
     * executed. In particular, for each shape, sent or received, whose centre
     * is not in the current LC, it is removed from the list of controlled
     * entities. In this way, all the subsequent operations are executed
     * <i>only</i> on the centring entities of the LC.
     */
    private void reset() {
        for (Entity e : sentEntities)
            if (!(e instanceof WallEntity))
                if (!lcBoundingBox.intersect(e.getCenter())) {
                    // System.out.println(e.getCenter());
                    entityList.remove(e);
                }
        //
        for (Entity e : receivedEntities)
            if (!(e instanceof WallEntity))
                if (!lcBoundingBox.intersect(e.getCenter()))
                    entityList.remove(e);
        //
        sentEntities.clear();
        receivedEntities.clear();
    }

    private void endCurrentIteration() {
        InteractionContainer ic = new InteractionContainer(new StepProtocol());
        Object[] params = new Object[1];
        params[0] = iterationCommands;
        ic.makeInteraction(StepProtocol.INTERACTIONEND, params);
        boolean status = Messages.sendInteraction(this, gcID, ic);
        if (!status)
            System.out.println("error"); // TODO something
    }

    /**
     * Manages the evolution of the simulation in the {@code LocalCoordinator}
     * on the basis of the minimal timestep returned by the
     * {@code GlobalCoordinator}. If the time returned is <i>smaller</i> than
     * the smallest time (among {@code first time of contact}- {@code ftc} - and
     * {@code first time of split} - {@code fts}) the coordinator advances to
     * such time. Instead, if the time returned by the {@code GlobalCoordinator}
     * is equal to the {@code ftc} ({@code fts} respectively), the coordinator
     * advances to such time and the collision is resolved (the split executed,
     * respectively).
     * 
     * @param trueStep
     *            The amount of time that can elapse among all the
     *            {@code LocalCoordinator}s of the simulation
     */
    private void advance(double trueStep) {
        moveEntities(trueStep); // <-- in any case....
        // TODO Luca: per ora chiamo direttamente solvecollisions
        solveCollisions(narrowPhaseModule.getPairs());
        // delta = FTC = FTS
//        if (trueStep == iterationFtc && trueStep == iterationFts) {
//            solveCollisions(narrowPhaseModule.getPairs());
//            executeSplits();
//            // (delta = FTC) < FTS
//        } else if (trueStep == iterationFtc) {
//            solveCollisions(narrowPhaseModule.getPairs());
//            // (delta = FTS) < FTC
//        } else if (trueStep == iterationFts) {
//            executeSplits();
//        }
    }

    private void executeSplits() {
        // TODO implements splits checking
    }

    private double receiveRealStep() {
        Message<?> msg = Messages.receivedInteraction(this, gcID,
                StepProtocol.INTERACTIONACKSTEP);
        if (msg != null) {
            InteractionContainer ic = (InteractionContainer) msg.getObject();
            return ((Double) ic.getParameter("Step")).doubleValue();
        }
        System.out.println("Local Coordinator: error in receiving real ftc!"); // TODO something
        return Double.NEGATIVE_INFINITY;
    }

    /**
     * Receives the {@code WallEntity} objects and send back an ack to the
     * {@code SpaceManager}.
     */
    @SuppressWarnings("unchecked")
    private boolean receiveWalls() {
        Message<?> msg = Messages.receivedInteraction(this, spaceId,
                SplitSpaceProtocol.INTERACTIONSENDWALLS);
        if (msg != null) {
            InteractionContainer spaceIc = (InteractionContainer) msg
                    .getObject();
            localWalls = (ArrayList<WallEntity>) spaceIc
                    .getParameter(SplitSpaceProtocol.INTERACTIONSENDWALLS);
            entityList.addAll(localWalls);
            return true;
        }
        terminate();
        return false;
    }

    private void terminate() {
        Messages.sendAck(this, inputId);
        simulate = false;
    }

    /**
     * Receive the data from the SM:
     * <ol>
     * <li>Coordinator bounds</li>
     * <li>Coordinator entities</li>
     * <li>scale (both spatial and temporal)</li>
     * </ol>
     * 
     * @return The data to set up the simulation.
     */
    private Object[] getSimulationData() {
        Message<?> msg = Messages.receivedInteraction(this, spaceId,
                SplitSpaceProtocol.INTERACTIONSENDLOCALSDATA);
        if (msg == null) {
            HermesLogger.log(HermesLogger.ERROR, this.getAgentName()
                    + " - Error while receiving data from SpaceManager agent.");
            terminate();
            return null;
        }
        //
        InteractionContainer spaceIc = (InteractionContainer) msg.getObject();
        Object[] data = (Object[]) spaceIc.getParameter("SendLocalsData");
        return data;
    }

    /**
     * Moves the entities to the real timestep on the basis of the
     * <i>overall</i> simulation timestep sent by the {@code GlobalCoordinator}.
     * <i>All</i> the entities are moved, except the {@code WallEntity} which
     * are fixed.<br>
     * Note also that this method update the the {@code Bounds} object
     * associated with the entity so that its position reflects the current
     * position of the {@code Entity}.
     * 
     * @param deltaT
     *            the real timestep calculated by the {@code GlobalCoordinator}
     */
    private void moveEntities(double deltaT) {
        for (Entity e : entityList) {
            if (e instanceof WallEntity)
                continue;
            //
            // System.out.println("Moving entity " + e);
            e.move(deltaT);
            e.getBounds().moveTo(e.getCenter());
        }
    }

    /**
     * Calculates the set of entities which are <i>not</i> completely housed in
     * the current {@code LocalCoordinator}. Such entities will be
     * <i>phantom</i> entities for this local coordinator, i.e. they will be
     * managed by it, even if they do not belong to the coordinator any more.
     * Given an {@code Entity}, the code simply checks if the AABB bounding
     * volume of the space represented by the {@code LocalCoordinator} contains
     * completely the AABB of the entity itself. If not, the entity is a phantom
     * one and should be sent to the {@code SpaceManager} to check which
     * {@code LocalCoordinator}s contain part of it.
     */
    private void collectPhantoms() {
        for (Entity e : this.entityList) {
            if (!(e instanceof WallEntity))
                if (!lcBoundingBox.contains(e.getBounds()))
                    sentEntities.add(e);
        }
    }

    /**
     * Sends non centring entities of this LC to the {@code SpaceManager} which
     * checks for their ownership, i.e. it checks for the LC owning the entities
     * centre.
     */
    // TODO così andrà bene? Mandare l'arraylist?
    private void sendEntitiesToCheck() {
        InteractionContainer ic = new InteractionContainer(
                new PhantomProtocol());
        Object[] params = new Object[1];
        params[0] = sentEntities;
        ic.makeInteraction(PhantomProtocol.INTERACTIONTARGETREQ, params);
        boolean status = Messages.sendInteraction(this, spaceId, ic);
        if (!status)
            System.out.println("error"); // TODO something
    }

    /**
     * Retrieves entities which are phantom and centering to the LC. Obtains
     * also entities given by LC neighbourhood. Then, it checks for presence of
     * entities sent out and if present updates with the new entities just
     * received.
     */
    @SuppressWarnings("unchecked")
    private void receivePhantoms() {
        Message<?> msg = Messages.receivedInteraction(this, spaceId,
                PhantomProtocol.INTERACTIONTARGETRESPPHANTOM);
        if (msg != null) {
            InteractionContainer ic = (InteractionContainer) msg.getObject();
            receivedEntities = (ArrayList<Entity>) ic
                    .getParameter("TargetRespPhantom");
            return;
        }
        terminate();
    }

    /**
     * Sends the minimal timestep for the {@code LocalCoordinator}.
     * {@code Double#MIN_VALUE} is sent if no collision or splitting occurs
     * along this timestep.
     */
    private void sendSmallestStep() {
        double step = (iterationFtc < iterationFts) ? iterationFtc
                : iterationFts;
        step = (delta < step) ? delta : step;
        InteractionContainer ic = new InteractionContainer(new StepProtocol());
        ic.makeInteraction(StepProtocol.INTERACTIONSENDSTEP, new Double(step));
        boolean status = Messages.sendInteraction(this, gcID, ic);
        if (!status)
            System.out
                    .println("Local coordinator: error in communicating local ftc");
    }

    /**
     * Resolves collisions according to the collision information collected
     * during the narrow-phase of collision detection.
     * 
     * @param list
     *            {@code EntityPair} list of objects representing couples of
     *            {@code Entity} shapes which collided in the current timestep.
     */
    //
    private void solveCollisions(List<EntityPair> list) {
        // TODO la compatibility check va fatta qui!
        for (EntityPair pair : list) {
            collisionResponder.resolveElastically(pair);

            // if (pair.e1.getClass() == WallEntity.class
            // || pair.e2.getClass() == WallEntity.class) {
            // collisionResponder.resolveElastically(pair);
            // continue;
            // }
            // collisionResponder.resolveInelastically(pair);
            // System.err
            // .println("VAIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII!!");
            // entityList.remove(pair.e1);
            // entityList.remove(pair.e2);
            // pair.e1.join(pair.e2);
            // entityList.add(pair.e1);
            // // TODO devo sincronizzarmi con gli altri locals se una delle due
            // è
            // // PHANTOM!!
        }
    }

    /**
     * Receives the {@code Delta} for the next simulator iteration. If something
     * goes wrong a non-number is returned.
     * 
     * @return the next {@code Delta} step of simulation or {@code NaN} if
     *         something goes wrong
     */
    private double receiveDelta() {

        Message<?> msg = Messages.receivedInteraction(this, gcID,
                StepProtocol.INTERACTIONSTART);
        if (msg == null)
            return Double.NaN;
        //
        boolean res = Messages.sendAck(this, gcID);
        if (!res)
            System.out.println("Local:Invio ack2 a "
                    + spaceId.getBornPlaceAddress().getIp() + " fallita!!");
        InteractionContainer ic = (InteractionContainer) msg.getObject();
        return ((Double) ic.getParameter("Delta")).doubleValue();
    }

    /**
     * @return the number of simulation timesteps currently elapsed in this
     *         {@code LocalCoordinator}
     */
    public double getMyTime() {
        return myTime;
    }

    /**
     * @return the list of currently available {@code Entity} objects on this
     *         {@code LocalCoordinator}
     */
    public List<Entity> getEntityList() {
        return entityList;
    }

    /**
     * @return the scale
     */
    public String getScale() {
        return scale.toString();
    }
}